
#include <cmath>
#include <BStream>
#include "member_BJsonDocument.h"
#include "member_BXmlNode.h"

using namespace BWE;

member_BJsonDocument::member_BJsonDocument(BJsonDocument* jsonDocument)
{
	boss = jsonDocument;
	pos = 0;
}
member_BJsonDocument::~member_BJsonDocument()
{

}

BString& member_BJsonDocument::message()
{
	if (value.empty())
		value = new BString();
	BString& text = value;
	return text;
}

BString member_BJsonDocument::serialize(const BValue& value, int indent)
{
	BString oss;

	if (value.empty())
	{
		oss << "null";
	}
	else if (value.is<bool>())
	{
		oss << ((value == true) ? "true" : "false");
	}
	else if (value.is<int>())
	{
		oss << (int)value;
	}
	else if (value.is<float>())
	{
		float val = value;
		if (std::isinf(val) || std::isnan(val))
		{
			oss << "null";
		}
		else
		{
			oss << val;
		}
	}
	else if (value.is<BString>())
	{
		oss << "\"";
		BString& str = value;
		for (int i = 0; i < str.size(); i++)
		{
			char c = str[i];
			switch (c) {
			case '\"': oss << "\\\""; break;
			case '\\': oss << "\\\\"; break;
			case '\b': oss << "\\b"; break;
			case '\f': oss << "\\f"; break;
			case '\n': oss << "\\n"; break;
			case '\r': oss << "\\r"; break;
			case '\t': oss << "\\t"; break;
			default:
				if (static_cast<unsigned char>(c) < 0x20)
				{
					oss << "\\u" << static_cast<int>(c);
				}
				else
				{
					oss << c;
				}
			}
		}
		oss << "\"";
	}
	else if (value.is<BJsonArray>())
	{
		const BJsonArray& array = value;
		oss << "[";
		for (int i = 0; i < array.size(); ++i)
		{
			if (i > 0)
				oss << ",";
			if (indent > 0)
				oss << "\n" << BString(' ', indent + 2);
			oss << serialize(array[i], indent > 0 ? indent + 2 : 0);
		}
		if (indent > 0 && !array.empty())
			oss << "\n" << BString(' ', indent);
		oss << "]";
	}
	else if (value.is<BJsonObject>())
	{
		const BJsonObject& valueMap = value;
		oss << "{";
		int i = 0;
		for (auto it = valueMap.begin(); it.valid(); ++it)
		{
			if (i++ > 0)
				oss << ",";
			if (indent > 0)
				oss << "\n" << BString(' ', indent + 2);
			oss << "\"" << it.key() << "\":" << (indent > 0 ? " " : "");
			oss << serialize(it.value(), indent > 0 ? indent + 2 : 0);
		}
		if (indent > 0 && !valueMap.empty())
			oss << "\n" << BString(' ', indent);
		oss << "}";
	}

	return oss;
}

char member_BJsonDocument::peek(const BString& text) const
{
	return pos < text.size() ? text[pos] : '\0';
}
char member_BJsonDocument::advance(const BString& text)
{
	return pos < text.size() ? text[pos++] : '\0';
}
void member_BJsonDocument::skipSpace(const BString& text)
{
	while (pos < text.size())
	{
		if (!std::isspace(text[pos]))
			break;
		++pos;
	}
}

BValue member_BJsonDocument::parseObject(const BString& text)
{
	BJsonObject* result = new BJsonObject;
	advance(text);

	while (true)
	{
		skipSpace(text);
		if (peek(text) == '}')
		{
			advance(text);
			break;
		}

		if (peek(text) != '\"')
		{
			message() << "Expected string key in object" << '\n';
			return result;
		}

		BString key = parseString(text);

		skipSpace(text);
		if (advance(text) != ':')
		{
			message() << "Expected ':' after key" << '\n';
			return result;
		}

		skipSpace(text);
		result->insert(key, parseValue(text));
		skipSpace(text);

		if (peek(text) == ',')
		{
			advance(text);
		}
		else if (peek(text) != '}')
		{
			message() << "Expected ',' or '}' in object" << '\n';
			return result;
		}
	}

	return result;
}
BValue member_BJsonDocument::parseArray(const BString& text)
{
	BJsonArray* result = new BJsonArray();
	advance(text);

	while (true)
	{
		skipSpace(text);
		if (peek(text) == ']')
		{
			advance(text);
			break;
		}

		result->append(parseValue(text));
		skipSpace(text);

		if (peek(text) == ',')
		{
			advance(text);
		}
		else if (peek(text) != ']')
		{
			message() << "Expected ',' or ']' in array" << '\n';
			return result;
		}
	}

	return result;
}
BValue member_BJsonDocument::parseValue(const BString& text)
{
	skipSpace(text);
	char c = peek(text);

	switch (c)
	{
	case '{':
		return parseObject(text);
	case '[':
		return parseArray(text);
	case '\"':
		return parseString(text);
	case 't':
	case 'f':
	case 'n':
		return parseKeyword(text);
	default:
		if (std::isdigit(c) || c == '-')
		{
			advance(text);
			return parseNumber(text);
		}
		message() << "Unexpected character: " << c;
		break;
	}
	return BValue();
}
BValue member_BJsonDocument::parseNumber(const BString& text)
{
	int start = pos - 1;

	while (pos < text.size())
	{
		char c = peek(text);
		if (!(std::isdigit(c) || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-'))
		{
			break;
		}
		advance(text);
	}

	BString numStr;
	text.substr(numStr, start, pos);
	numStr.lower();

	if (numStr.contain('e'))
	{
		if (numStr.endWith('f'))
			return strtof(numStr.cstr(), 0);
		return strtod(numStr.cstr(), 0);
	}
	if (numStr.contain('.'))
	{
		if (numStr.endWith('f'))
			return numStr.toFloat();
		return numStr.toDouble();
	}
	return numStr.toInt();
}
BValue member_BJsonDocument::parseKeyword(const BString& text)
{
	BString keyword;
	while (pos < text.size() && std::isalpha(peek(text)))
	{
		keyword << advance(text);
	}

	if (keyword == "true")
		return true;
	if (keyword == "false")
		return false;
	if (keyword == "null")
		return nullptr;

	return BValue();
}
BValue member_BJsonDocument::parseString(const BString& text)
{
	BString* result = new BString();
	char quote = advance(text);

	while (pos < text.size())
	{
		char c = advance(text);
		if (c == quote)
			break;

		if (c == '\\')
		{
			c = advance(text);
			switch (c)
			{
			case '\"':
				result->append('\"'); break;
			case '\\':
				result->append('\\'); break;
			case '/':
				result->append('/'); break;
			case 'b':
				result->append('\b'); break;
			case 'f':
				result->append('\f'); break;
			case 'n':
				result->append('\n'); break;
			case 'r':
				result->append('\r'); break;
			case 't':
				result->append('\t'); break;
			case 'u':
			{
				pos += 4;
				result->append('?');
				break;
			}
			default: result->append(c);
			}
		}
		else
		{
			result->append(c);
		}
	}

	return result;
}
